"""
fetch_cn_unis.py

功能：
1) 从教育部高校名单页面抓取高校列表（示例：手动配置或从教育部页面解析）。
2) 针对每个高校，尝试获取其主页并抓取“资源链接”（图书馆、招生、教务、学院、关于等）。
3) 输出 CSV 和 JSON 文件。

注意：
- 请遵守 robots.txt、网站使用条款以及礼貌抓取规则。
- 这只是一个通用模板，针对不同高校网站可能需要特殊处理或更复杂的解析逻辑。
"""

import time
import random
import csv
import json
import re
from urllib.parse import urljoin, urlparse
import urllib.robotparser

import requests
from bs4 import BeautifulSoup
import pandas as pd

# ---------- 配置 ----------
HEADERS = {
    "User-Agent": "Mozilla/5.0 (compatible; UniversityResourceBot/1.0; +https://example.com/bot)"
}
MIN_DELAY = 1.0   # 抓取每个站点的最小等待秒数
MAX_DELAY = 3.0   # 最大等待秒数
TIMEOUT = 15      # requests timeout
OUTPUT_CSV = "university_resources.csv"
OUTPUT_JSON = "university_resources.json"

# 教育部全国高等学校名单（示例页面 — 可替换为最新页面）
MOE_UNI_LIST_URL = "https://www.moe.gov.cn/jyb_xxgk/s5743/s5744/A03/202406/t20240621_1136990.html"
# 注意：上面的页面为示例；脚本中有函数可以直接从该页面抓取高校名称（若页面结构变化，需要调整解析）

# 要查找的资源关键词（英文与中文都包含）
RESOURCE_KEYWORDS = [
    "library", "lib", "图书馆",
    "admission", "招生", "本科招生", "研究生招生",
    "about", "关于", "简介", "学校概况",
    "department", "学院", "院系", "系",
    "教务", "教务处", "教学",
    "科研", "研究", "研究生院",
    "portal", "学生", "教师"
]

# ---------- 工具函数 ----------
def polite_sleep():
    time.sleep(random.uniform(MIN_DELAY, MAX_DELAY))

def is_allowed_by_robots(base_url, user_agent=HEADERS["User-Agent"], path="/"):
    """
    尝试读取 robots.txt 并判断是否允许抓取 path。
    """
    parsed = urlparse(base_url)
    robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
    rp = urllib.robotparser.RobotFileParser()
    try:
        rp.set_url(robots_url)
        rp.read()
        return rp.can_fetch(user_agent, path)
    except Exception:
        # 如果无法读取 robots.txt，则默认返回 False 更安全（也可改为 True）
        return False

def guess_homepage_from_search_term(name):
    """
    简单启发式：使用校园名猜测域名，比如 '北京大学' -> 'pku.edu.cn' 并尝试访问常见域名（尝试列表）。
    也可以通过搜索引擎 API 获得更精确的主页（此脚本未调用搜索API）。
    """
    # 这里仅返回 None 表示不猜测（保守）。用户可以扩展此函数使用搜索API或本地映射文件。
    return None

def fetch_url(url):
    try:
        r = requests.get(url, headers=HEADERS, timeout=TIMEOUT, allow_redirects=True)
        r.raise_for_status()
        return r
    except Exception as e:
        # 可记录日志
        return None

def extract_resource_links_from_html(base_url, html_text):
    soup = BeautifulSoup(html_text, "html.parser")
    links = []
    for a in soup.find_all("a", href=True):
        href = a.get("href").strip()
        # 过滤 javascript: mailto: 等
        if href.lower().startswith("javascript:") or href.lower().startswith("mailto:"):
            continue
        # 归一化为绝对 URL
        full = urljoin(base_url, href)
        text = (a.get_text(strip=True) or "")
        links.append({"text": text, "url": full})
    # 根据关键词筛选并打标签
    matched = []
    for item in links:
        combined = (item["text"] + " " + item["url"]).lower()
        for kw in RESOURCE_KEYWORDS:
            if kw.lower() in combined:
                matched.append(item)
                break
    # 去重（按 URL）
    seen = set()
    filtered = []
    for it in matched:
        u = it["url"]
        if u not in seen:
            seen.add(u)
            filtered.append(it)
    return filtered

# ---------- 主流程 ----------
def parse_moe_university_list_page(html_text, base_url):
    """
    从教育部页面解析高校名称。不同页面结构不同，需要按实际页面调整。
    当前实现：尽量提取页面中以 <li>、<p> 或表格里的学校名文本。
    """
    soup = BeautifulSoup(html_text, "html.parser")
    names = set()

    # 常见：页面里可能有 <ul><li> 学校名
    for li in soup.find_all("li"):
        t = li.get_text(" ", strip=True)
        if t and len(t) < 200:
            # 简单过滤：含有“大学/学院/高校”等关键词
            if re.search(r"(大学|学院|高校|学院|医科大学|师范大学|理工大学|财经大学)", t):
                names.add(t)

    # 备选：p 标签
    for p in soup.find_all("p"):
        t = p.get_text(" ", strip=True)
        if t and len(t) < 200:
            if re.search(r"(大学|学院|高校|学院|医科大学|师范大学|理工大学|财经大学)", t):
                names.add(t)

    # 若未找到，作为回退，尝试抓取所有表格单元格文本
    if not names:
        for td in soup.find_all(["td", "th"]):
            t = td.get_text(" ", strip=True)
            if t and re.search(r"(大学|学院|高校)", t):
                names.add(t)

    # 清洗并返回列表
    out = []
    for n in names:
        # 去掉编号或括号内编号
        n_clean = re.sub(r"^\s*\d+\.?", "", n)
        out.append(n_clean.strip())
    return sorted(out)

def discover_homepage_by_search(name):
    """
    占位函数：如果你想集成搜索引擎（例如 Bing Search API / Google Custom Search / 教育部站内搜索）
    可以在这里实现。当前返回 None，表示必须手动提供或依赖教育部页面内链接。
    """
    return None

def fetch_resources_for_university(name, candidate_homepage=None):
    """
    对单个高校尝试获取主页并抓取资源链接。
    candidate_homepage: 如果已知学校主页，可以传入（字符串）
    返回 dict：{name, homepage, resources: [{text,url}], status, error}
    """
    result = {"name": name, "homepage": None, "resources": [], "status": "not_found", "error": None}
    tried = []

    # 如果提供了 candidate_homepage，优先尝试
    candidates = []
    if candidate_homepage:
        candidates.append(candidate_homepage)

    # 如果教育部页面或其他地方给出链接，这里可以追加（示例未实现）

    # 最后加入猜测（空）
    guessed = guess_homepage_from_search_term(name)
    if guessed:
        candidates.append(guessed)

    # 如果没有候选，直接返回（用户可以扩展为接入搜索 API）
    if not candidates:
        result["status"] = "no_candidate"
        result["error"] = "no candidate homepage; consider providing homepages or integrate search API"
        return result

    for homepage in candidates:
        tried.append(homepage)
        # 检查 robots.txt
        if not is_allowed_by_robots(homepage):
            result["error"] = f"blocked_by_robots: {homepage}"
            continue

        polite_sleep()
        r = fetch_url(homepage)
        if not r:
            continue

        result["homepage"] = r.url  # 跟随重定向后的 URL
        html = r.text
        resources = extract_resource_links_from_html(result["homepage"], html)
        result["resources"] = resources
        result["status"] = "ok"
        return result

    result["status"] = "failed"
    result["error"] = f"tried {len(tried)} candidate(s)"
    return result

# ---------- 示例驱动流程 ----------
def main_demo():
    print("开始：从教育部示例页面抓取高校列表（仅作示例）...")
    r = fetch_url(MOE_UNI_LIST_URL)
    if not r:
        print("无法访问教育部示例页面，请手动准备高校名单或更新 MOE_UNI_LIST_URL。")
        return

    uni_names = parse_moe_university_list_page(r.text, MOE_UNI_LIST_URL)
    print(f"解析到高校数量（去重后样本）：{len(uni_names)}（仅显示前 30）")
    for n in uni_names[:30]:
        print(" -", n)

    # 为演示目的，只对前 N 个高校抓取资源（避免长时间运行）
    N = 20
    sample_unis = uni_names[:N]

    results = []
    for name in sample_unis:
        print(f"\n处理：{name}")
        # 这里我们没有实现自动搜索首页；为示例假定教育部页面内含链接并从中提取（未实现）
        # 因此我们演示如何处理已知首页的情况：用户可提供字典 mapping{name: homepage}
        homepage_override = None
        # homepage_override = "https://www.example.edu.cn"  # <- 如已知可设置
        res = fetch_resources_for_university(name, candidate_homepage=homepage_override)
        print("  状态:", res["status"], "资源数:", len(res["resources"]))
        if res["resources"]:
            for it in res["resources"][:5]:
                print("    ", it["text"], "->", it["url"])
        if res["error"]:
            print("  错误:", res["error"])
        results.append(res)

    # 保存结果
    df_rows = []
    for r in results:
        row = {
            "name": r["name"],
            "homepage": r["homepage"] or "",
            "status": r["status"],
            "error": r["error"] or "",
            "resources": json.dumps(r["resources"], ensure_ascii=False)
        }
        df_rows.append(row)

    df = pd.DataFrame(df_rows)
    df.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig")
    with open(OUTPUT_JSON, "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    print("\n完成。输出文件：", OUTPUT_CSV, OUTPUT_JSON)

if __name__ == "__main__":
    main_demo()
